﻿using System;
using System.Data;
using System.Data.SqlClient;
using System.IO;

namespace KvDbApi
{
    public class ValueHandle : IEquatable<IValueHandle>, IValueHandle
    {
        private TimeSpan? ttlMinutes;

        public ValueHandle(string key)
        {
            this.Key = key;
            this.HardenToDisk = true;
        }

        public ValueHandle(string key, int version)
            : this(key)
        {
            this.Version = version;
        }

        public string Key { get; private set; }

        public int? Version { get; set; }

        public ValueState LastKnownState { get; private set; }

        public DateTimeOffset? NextExpiry { get; private set; }

        public bool HardenToDisk { get; set; }

        public TimeSpan TimeToLive 
        {
            get
            {
                return this.ttlMinutes.HasValue ? this.ttlMinutes.Value : ExpirationPolicy.DefaultTimeToLive;
            }

            set
            {
                this.ttlMinutes = value;
            }
        }

        internal object DbVersion
        {
            get
            {
                return this.Version.HasValue ? (object)this.Version.Value : DBNull.Value;
            }
        }

        public Stream GetReaderStream(IDbConnection connection)
        {
            return new KeyValueReaderStream(connection, this);
        }

        public Stream GetWriterStream(IDbConnection connection)
        {
            return new KeyValueWriterStream(connection, this);
        }

        public int Touch(IDbConnection connection, TimeSpan? newTimeToLive = null)
        {
            if (newTimeToLive.HasValue)
            {
                this.TimeToLive = newTimeToLive.Value;
            }

            SqlParameter newVersion;
            SqlParameter newExpiry;
            using (var command = connection.CreateKvTouch(this, out newVersion, out newExpiry))
            {
                var rowsAffected = command.ExecuteNonQuery();
                this.SetValueMetadata(ValueState.Memory, newVersion, newExpiry);
                return rowsAffected;
            }
        }

        public int Move(IDbConnection connection, TimeSpan? newTimeToLive = null)
        {
            if (newTimeToLive.HasValue)
            {
                this.TimeToLive = newTimeToLive.Value;
            }

            SqlParameter newVersion;
            SqlParameter newExpiry;
            using (var command = connection.CreateKvMove(this, out newVersion, out newExpiry))
            {
                var rowsAffected = command.ExecuteNonQuery();
                this.SetValueMetadata(ValueState.Memory, newVersion, newExpiry);
                return rowsAffected;
            }
        }

        public int Delete(IDbConnection connection, bool destroy)
        {
            using (var command =
                destroy ?
                connection.CreateKvDestroy(this) :
                connection.CreateKvDelete(this))
            {
                return command.ExecuteNonQuery();
            }
        }

        public override string ToString()
        {
            return "{ " + this.Key + (this.Version.HasValue ? ":" + this.Version.Value : string.Empty) + " }";
        }

        public bool Equals(IValueHandle other)
        {
            if (other == null)
            {
                return false;
            }

            if (ReferenceEquals(this, other))
            {
                return true;
            }

            return this.Key == other.Key;
        }

        public override bool Equals(object obj)
        {
            return this.Equals(obj as IValueHandle);
        }

        public override int GetHashCode()
        {
            return this.Key.GetHashCode();
        }

        internal void NullifyVersion()
        {
            this.Version = null;
        }

        internal void SetValueMetadata(ValueState state, SqlParameter versionParam, SqlParameter expiryParam)
        {
            DateTime? nextExpiry = null;
            if (expiryParam.Value != null && expiryParam.Value != DBNull.Value)
            {
                nextExpiry = (DateTime)expiryParam.Value;
            }

            this.LastKnownState = state;
            this.NextExpiry = nextExpiry;
            this.SetVersionMetadata(versionParam);
        }

        internal void SetVersionMetadata(SqlParameter versionParam)
        {
            int? version = null;
            if (versionParam.Value != null && versionParam.Value != DBNull.Value)
            {
                version = (int)versionParam.Value;
            }

            if (version.HasValue)
            {
                if (version.Value == Usage.ValueNotFound)
                {
                    this.LastKnownState = ValueState.NotFound;
                }
            }

            this.Version = version;
        }

        internal void SetNotFound()
        {
            this.LastKnownState = ValueState.NotFound;
            this.Version = Usage.ValueNotFound;
        }
    }
}
